﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.Internals;
using System.IO;
using MacomberMapSystem.Common.CIM;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// (C) 2012, Michael E. Legatt, Ph.D., Electric Reliability Council of Texas
    /// This class provides a contingency delta report
    /// </summary>
    public class CIM_ContingencyDeltaReport: CIM_Processor
    {
        #region Initialization
        /// <summary>
        /// Initialize a new contingency delta report
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="NamedParameters"></param>
        /// <param name="Repository"></param>
        public CIM_ContingencyDeltaReport(XmlElement xElem, Dictionary<String, Object> NamedParameters, MM_Repository Repository)
            : base(xElem, NamedParameters, Repository)
        {
        }
        #endregion

        /// <summary>
        /// Report our process name
        /// </summary>
        public override string ProcessName
        {
            get { return "Contingency Delta Report"; }
        }

        /// <summary>
        /// Initiate our delta process
        /// </summary>
        /// <param name="DbConn"></param>
        /// <returns></returns>
        public override enumProcessState InitiateProcess(MacomberMapSystem.Common.Database.MM_Database_Connector DbConn)
        {
            //First, build our list of elements to be processed
            MM_Database_Model Model = NamedParameters["Model"] as MM_Database_Model;
            Total = Model.Traces.Count;
            try
            {
                AnalyzeCtgDiffs(ProcessString(Path.Combine(TargetFolder, TargetFileName)), Model, DbConn, Repository);
                return enumProcessState.Completed;
            }
            catch (Exception ex)
            {
                base.ProcessLog.AppendLine("Error: " + MM_System_Interface.ConvertError(ex));
                return enumProcessState.FailedWithErrors;
            }
        }

        /// <summary>
        /// Analyze the differences between the current and prior models
        /// </summary>
        /// <param name="DeltaLog"></param>
        /// <param name="Model"></param>
        /// <param name="DbConn"></param>
        /// <param name="Repository"></param>
        public void AnalyzeCtgDiffs(string DeltaLog, MM_Database_Model Model, MM_Database_Connector DbConn, MM_Repository Repository)
        {
            MM_Database_Model OldModel = Model.PriorModel;
            if (!OldModel.EnsureOpened())
                return;

            using (XmlTextWriter xW = new XmlTextWriter(DeltaLog, new UTF8Encoding(false)))
            {
                xW.WriteStartDocument();
                xW.Formatting = Formatting.Indented;
                xW.WriteStartElement("Contingency_Delta");
                xW.WriteAttributeString("NewModel", Model.NameWithoutExtension);
                xW.WriteAttributeString("OldModel", Model.PriorModel.NameWithoutExtension);
                xW.WriteAttributeString("RunOn", Environment.MachineName);
                xW.WriteAttributeString("RunBy", Environment.UserDomainName + "\\" + Environment.UserName);
                xW.WriteAttributeString("RunAt", XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Unspecified));

                //Read through our prior model's contingencies
                Dictionary<CIM_RdfID, CIM_Trace[]> OldTraces = new Dictionary<CIM_RdfID, CIM_Trace[]>();
                foreach (CIM_Trace Trace in OldModel.Traces.Values)
                    foreach (CIM_RdfID rdfID in Trace.rdfIDs)
                        if (OldTraces.ContainsKey(rdfID))
                        {
                            List<CIM_Trace> Traces = new List<CIM_Trace>(OldTraces[rdfID]);
                            Traces.Add(Trace);
                            OldTraces[rdfID] = Traces.ToArray();
                        }
                        else
                            OldTraces.Add(rdfID, new CIM_Trace[] { Trace });


                //Now, go through each contingency definition, and trace what happened to it
                Total = Model.Traces.Count;
                Current = 0;
                foreach (CIM_Trace Trace in Model.Traces.Values)
                {
                    Current++;
                    //Start by mapping each element in the contingency against the contingencies to which it may belong
                    List<CIM_Element> NewElements = new List<CIM_Element>();
                    List<CIM_Element> AddedElements = new List<CIM_Element>();
                    List<CIM_Element> RemovedElements = new List<CIM_Element>();
                    Dictionary<String, List<CIM_Element>> SplitContingencies = new Dictionary<string, List<CIM_Element>>();
                    Dictionary<String, List<CIM_RdfID>> CtgMaps = new Dictionary<string, List<CIM_RdfID>>();
                    CIM_Trace[] FoundCtg;
                    bool NameMatch = false;
                    List<CIM_RdfID> FoundCtgMaps;
                    foreach (CIM_RdfID rdfID in Trace.rdfIDs)
                        if (!OldTraces.TryGetValue(rdfID, out FoundCtg))
                            NewElements.Add(Model.FromRdfID(rdfID));
                        else
                            foreach (CIM_Trace Ctg in FoundCtg)
                            {
                                if (!CtgMaps.TryGetValue(Ctg.Name, out FoundCtgMaps))
                                    CtgMaps.Add(Ctg.Name, FoundCtgMaps = new List<CIM_RdfID>(5));
                                FoundCtgMaps.Add(rdfID);
                            }

                    //If we have a perfect name map, just compare elements
                    if (CtgMaps.TryGetValue(Trace.Name, out FoundCtgMaps))
                    {
                        foreach (CIM_Element Elem in Trace.Elements)
                            if (!FoundCtgMaps.Contains(Elem.rdfID))
                                AddedElements.Add(Elem);

                        //Check against our root trace for removed ones
                        foreach (CIM_Element Elem in OldModel.Traces[Trace.Name].Elements)
                            if (Array.IndexOf(Trace.rdfIDs, Elem.rdfID) == -1)
                                RemovedElements.Add(Elem);

                        NameMatch = true;
                    }
                    else
                    {
                        //Now, check to see if we find a single match
                        NameMatch = false;


                        foreach (CIM_Element Elem in Trace.Elements)
                                foreach (KeyValuePair<String, List<CIM_RdfID>> kvp in CtgMaps)
                                    if (!kvp.Key.StartsWith("M") && !kvp.Key.StartsWith("D") && kvp.Value.Contains(Elem.rdfID))
                                        if (!SplitContingencies.ContainsKey(kvp.Key))
                                            SplitContingencies.Add(kvp.Key, new List<CIM_Element>(new CIM_Element[] { Elem }));
                                        else
                                            SplitContingencies[kvp.Key].Add(Elem);
                        
                        //Go through, and remove breaker-only ones
                        foreach (String str in new List<String>(SplitContingencies.Keys))
                        {
                            bool BreakerOnly = true;
                            foreach (CIM_Element Elem in SplitContingencies[str])
                                if (Elem.ElemType != "Switch" && Elem.ElemType != "Breaker")
                                    BreakerOnly = false;
                            if (BreakerOnly)
                                SplitContingencies.Remove(str);
                        }

                        if (Trace.Name == "XABE589")
                            Console.WriteLine("Watch me");

                        //If we have only one contingency, report out the list of missing ones
                        if (SplitContingencies.Count == 1)
                            foreach (CIM_Element Elem in OldModel.Traces[SplitContingencies.Keys.ToArray()[0]].Elements)
                                if (Array.IndexOf(Trace.rdfIDs, Elem.rdfID) == -1)
                                    RemovedElements.Add(Elem);

                        //Go through our list of elements and locate those not seen in the prior model
                        foreach (CIM_Element Elem in Trace.Elements)
                        {
                            bool FoundIt = false;
                            foreach (List<CIM_Element> Elems in SplitContingencies.Values)
                                if (!FoundIt && Elems.Contains(Elem))
                                    FoundIt = true;
                            if (!FoundIt)
                                AddedElements.Add(Elem);
                        }                 
                    }
                   

                    if (NameMatch && NewElements.Count + AddedElements.Count + RemovedElements.Count > 0)
                    {
                        xW.WriteStartElement("Contingency");
                        xW.WriteAttributeString("Name", Trace.Name);
                        xW.WriteAttributeString("Elements", Trace.rdfIDs.Length.ToString());
                        xW.WriteAttributeString("PriorElements", OldModel.Traces[Trace.Name].rdfIDs.Length.ToString());
                       
                        foreach (CIM_Element Elem in NewElements)
                            WriteElementInformation(xW, Elem, "New_Element");

                        foreach (CIM_Element Elem in AddedElements)
                            if (!NewElements.Contains(Elem))
                                WriteElementInformation(xW, Elem, "Added_Element");

                        foreach (CIM_Element Elem in RemovedElements)
                            WriteElementInformation(xW, Elem, "Removed_Element");

                        xW.WriteEndElement();
                        xW.Flush();
                        System.Windows.Forms.Application.DoEvents();
                    }
                    else if (!NameMatch && SplitContingencies.Count == 1 && !OldModel.Traces.ContainsKey(SplitContingencies.Keys.ToArray()[0]) && (!Trace.Name.StartsWith("N_") || !SplitContingencies.Keys.ToArray()[0].StartsWith("N_") || AddedElements.Count + RemovedElements.Count + NewElements.Count > 0))
                    {
                        xW.WriteStartElement("Contingency");
                        xW.WriteAttributeString("Name", Trace.Name);
                        xW.WriteAttributeString("OldName", SplitContingencies.Keys.ToArray()[0]);
                        xW.WriteAttributeString("Elements", Trace.rdfIDs.Length.ToString());
                        xW.WriteAttributeString("PriorElements", OldModel.Traces[SplitContingencies.Keys.ToArray()[0]].rdfIDs.Length.ToString());
                        foreach (CIM_Element Elem in NewElements)
                            WriteElementInformation(xW, Elem, "New_Element");
                        foreach (CIM_Element Elem in AddedElements)
                            if (!NewElements.Contains(Elem))
                                WriteElementInformation(xW, Elem, "Added_Element");
                        foreach (CIM_Element Elem in RemovedElements)
                            WriteElementInformation(xW, Elem, "Removed_Element");
                        xW.WriteEndElement();
                        xW.Flush();
                        System.Windows.Forms.Application.DoEvents();

                    }
                    else if (!NameMatch)
                    {
                        xW.WriteStartElement("Contingency");
                        xW.WriteAttributeString("Name", Trace.Name);
                        xW.WriteAttributeString("Elements", Trace.rdfIDs.Length.ToString());
                        
                        foreach (CIM_Element Elem in NewElements)
                            WriteElementInformation(xW, Elem, "New_Element");
                        foreach (CIM_Element Elem in AddedElements)
                            if (!NewElements.Contains(Elem))
                                WriteElementInformation(xW, Elem, "Added_Element");
                        foreach (CIM_Element Elem in RemovedElements)
                            WriteElementInformation(xW, Elem, "Removed_Element");
                        foreach (KeyValuePair<String, List<CIM_Element>> kvp in SplitContingencies)
                        {
                            xW.WriteStartElement("SplitContingency");
                            xW.WriteAttributeString("OldName", kvp.Key);
                            xW.WriteAttributeString("PriorElements", OldModel.Traces[kvp.Key].rdfIDs.Length.ToString());
                            foreach (CIM_Element Elem in kvp.Value)
                                WriteElementInformation(xW, Elem, "Element");
                            xW.WriteEndElement();
                        }
                        xW.WriteEndElement();
                        xW.Flush();
                        System.Windows.Forms.Application.DoEvents();
                    }
                }
            }
        }

        /// <summary>
        /// Write out our element information
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="Elem"></param>
        /// <param name="Title"></param>
        private static void WriteElementInformation(XmlTextWriter xW, CIM_Element Elem, string Title)
        {
            xW.WriteStartElement(Title);
            xW.WriteAttributeString("Type", Elem.ElemType);
            xW.WriteAttributeString("TEID", Elem.TEID);
            if (Elem.Substation != null)
                xW.WriteAttributeString("Substation", Elem.Substation.Name);
            xW.WriteAttributeString("Name", Elem.LineName);
            if (Elem.TypeName == "cim:ACLineSegment")
            {
                xW.WriteAttributeString("FromSubstation", Elem.ACSegmentStations[0].Name);
                xW.WriteAttributeString("ToSubstation", Elem.ACSegmentStations[1].Name);
            }
            if (Elem.VoltageLevel != null)
                xW.WriteAttributeString("VoltageLevel", Elem.VoltageLevel.Name);
           
            xW.WriteEndElement();

        }

        
    }
}

